/*
 */
package org.actorsguildframework;

import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicInteger;

import junit.framework.Assert;

import org.actorsguildframework.annotations.ConcurrencyModel;
import org.actorsguildframework.annotations.Message;
import org.actorsguildframework.annotations.Model;
import org.junit.Test;

/**
 * Unit tests for Actor.
 */
public class ActorTest {
	/**
	 * Test actor.
	 */
	public static class MyActor extends Actor {
		private final AtomicInteger nowInLockedSection = new AtomicInteger(0);
		/**
		 * The number of locked sections that ran.
		 */
		public final AtomicInteger ranInLockedSection = new AtomicInteger(0);
		private final Object maxLock = new Object();
		/**
		 * Read the maximum number of agents that ran simultanously in the 
		 * lock section here.
		 */
		public volatile int maxInLockedSection;
		
		/**
		 * Tests the lock(Runnable) statement.
		 * @return nothing
		 */
		@Message
		@Model(ConcurrencyModel.MultiThreaded)
		public AsyncResult<Void> testMaxInLockedSectionRunnable() {
			lock(new Runnable() {
				public void run() {
					int n = nowInLockedSection.addAndGet(1);
					ranInLockedSection.addAndGet(1);
					synchronized (maxLock) {
						if (maxInLockedSection < n)
							maxInLockedSection = n;
					}
					try {
						Thread.sleep(500);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					nowInLockedSection.addAndGet(-1);
				}
			});
			return noResult();
		}
		
		/**
		 * Tests the lock(Callable) statement.
		 * @param v a value that will be returned, after being passed into the Callable
		 * and then returned from it.
		 * @return the argument
		 */
		@Message
		@Model(ConcurrencyModel.MultiThreaded)
		public AsyncResult<Long> testMaxInLockedSectionCallable(final long v) {
			Long r = lock(new Callable<Long>() {
				public Long call() {
					int n = nowInLockedSection.addAndGet(1);
					ranInLockedSection.addAndGet(1);
					synchronized (maxLock) {
						if (maxInLockedSection < n)
							maxInLockedSection = n;
					}
					try {
						Thread.sleep(500);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					nowInLockedSection.addAndGet(-1);
					return v;
				}
			});
			return result(r);
		}

		/**
		 * Tests the lock(Runnable) statement in a single-threaded context.
		 * @return true if the lock ran
		 */
		@Message
		@Model(ConcurrencyModel.SingleThreaded)
		public AsyncResult<Boolean> testLockSingleThreaded() {
			final Object lock = new Object();
			final boolean[] ranHolder = new boolean[1];
			
			lock(new Runnable() {
				public void run() {
					synchronized(lock) {
						ranHolder[0] = true;
					}
				}
			});
			synchronized (lock) {
				return result(ranHolder[0]);
			}
		}
	}
	
	/**
	 * Tests direct instantiation of MyActor.
	 */
	@Test(expected=ActorException.class)
	public void testDirectActorInstatiation() {
		new MyActor();
	}
	
	/**
	 * Tests {@link Actor#getAgent()}.
	 */
	@Test
	public void testGetAgent() {
		DefaultAgent a = new DefaultAgent();
		MyActor b = a.create(MyActor.class);
		Assert.assertSame(a, b.getAgent());
	}
	
	/**
	 * Tests {@link Actor#result(Object)}.
	 */
	@Test
	public void testResult() {
		DefaultAgent a = new DefaultAgent();
		MyActor b = a.create(MyActor.class);
		ImmediateResult<String> r = b.result("foo");
		Assert.assertTrue(r.isReady());
		Assert.assertEquals("foo", r.get());
	}
	
	/**
	 * Tests {@link Actor#noResult()}.
	 */
	@Test
	public void testNoResult() {
		DefaultAgent a = new DefaultAgent();
		MyActor b = a.create(MyActor.class);
		ImmediateResult<Void> r = b.noResult();
		Assert.assertTrue(r.isReady());
		Assert.assertEquals(null, r.get());
	}
	
	/**
	 * Tests {@link Actor#lock(Runnable)}.
	 */
	@Test
	public void testLockRunnable() {
		DefaultAgent a = new DefaultAgent();
		MyActor ma = a.create(MyActor.class);
		AsyncResult<Void> r1, r2, r3;
		r1 = ma.testMaxInLockedSectionRunnable();
		r2 = ma.testMaxInLockedSectionRunnable();
		r3 = ma.testMaxInLockedSectionRunnable();
		a.awaitAllAndThrow(r1, r2, r3);
		Assert.assertEquals(3, ma.ranInLockedSection.get());
		Assert.assertEquals(1, ma.maxInLockedSection);
	}
	
	/**
	 * Tests {@link Actor#lock(Runnable)}.
	 */
	@Test
	public void testLockCallable() {
		DefaultAgent a = new DefaultAgent();
		MyActor ma = a.create(MyActor.class);
		AsyncResult<Long> r1, r2, r3;
		r1 = ma.testMaxInLockedSectionCallable(22);
		r2 = ma.testMaxInLockedSectionCallable(1);
		r3 = ma.testMaxInLockedSectionCallable(-5);
		a.awaitAllAndThrow(r1, r2, r3);
		Assert.assertEquals(3, ma.ranInLockedSection.get());
		Assert.assertEquals(1, ma.maxInLockedSection);
		Assert.assertEquals(22L, r1.get().longValue());
		Assert.assertEquals(1L, r2.get().longValue());
		Assert.assertEquals(-5L, r3.get().longValue());
	}
	
	/**
	 * Tests {@link Actor#lock(Runnable)} in a single-threaded message.
	 */
	@Test
	public void testLockSingleThreaded() {
		DefaultAgent a = new DefaultAgent();
		MyActor ma = a.create(MyActor.class);
		Assert.assertTrue(ma.testLockSingleThreaded().get());
	}
}
